﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using Microsoft.Health;
using Microsoft.Health.Web;
using Microsoft.Health.ItemTypes;

using FusionAPI;
using System.IO;
using System.Xml.Serialization;
using System.Text;

namespace PFSync
{
    public static class SyncManager
    {
        /// <summary>
        /// Method to Sync ALL items between HealthVault and PracticeFusion
        /// 
        /// This method will generate a list of items for both, get a list
        /// of the items common to each and remove the items. What is left
        /// are the items unique to each and those items will be synced.
        /// </summary>
        /// <param name="HVConnection">HealthVault Connection</param>
        /// <param name="API">PatientFusion API</param>
        /// <param name="vendorID">PatientFusion Vendor ID</param>
        /// <param name="patientID">PatientFusion Patient Token</param>
        /// <returns></returns>
        public static bool SyncAll(OfflineWebApplicationConnection HVConnection, Guid RecordID,
            CLinicalData API, string VendorID, string PatientID)
        {
            List<SyncItem> HVItems = new List<SyncItem>();
            List<SyncItem> PFItems = new List<SyncItem>();
            List<SyncItem> CommonItems = new List<SyncItem>();
            HealthRecordItemCollection HealthVaultItems;
            ClinicalDataValue[] PatientFusionItems;
            OperationStatus FusionStatus;

            // Get the HealthVault accessor
            HealthRecordAccessor HRAccessor = new HealthRecordAccessor(HVConnection, RecordID);

            try
            {
                // Get all items from HealthVault first (starting with BloodGlucose)
                HealthVaultItems = HRAccessor.GetItemsByType(BloodGlucose.TypeId);
                foreach (BloodGlucose g in HealthVaultItems)
                    HVItems.Add(new SyncGlucose(g));

                // Do BloodPressure now
                HealthVaultItems = HRAccessor.GetItemsByType(BloodPressure.TypeId);
                foreach (BloodPressure p in HealthVaultItems)
                    HVItems.Add(new SyncPressure(p));

                // Do Weight now
                HealthVaultItems = HRAccessor.GetItemsByType(Weight.TypeId);
                foreach (Weight w in HealthVaultItems)
                    HVItems.Add(new SyncWeight(w));

                // Do Height now
                HealthVaultItems = HRAccessor.GetItemsByType(Height.TypeId);
                foreach (Height h in HealthVaultItems)
                    HVItems.Add(new SyncHeight(h));

                
                // Now grab a list of all of the PatientFusion SyncItems (starting with Glucose [mg/dL])
                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 59, out FusionStatus);
                foreach (ClinicalDataValue g in PatientFusionItems)
                    PFItems.Add(new SyncGlucose(g, MeasurementType.Milligrams));

                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 60, out FusionStatus);
                foreach (ClinicalDataValue g in PatientFusionItems)
                    PFItems.Add(new SyncGlucose(g, MeasurementType.Millimoles));

                // Do Pressure now
                foreach (SyncPressure p in GeneratorPressureList(API, VendorID, PatientID))
                    PFItems.Add(p);

                // Do Weight now (in Pounds first)
                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 82, out FusionStatus);
                foreach (ClinicalDataValue w in PatientFusionItems)
                    PFItems.Add(new SyncWeight(w, MeasurementType.Lbs));

                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 83, out FusionStatus);
                foreach (ClinicalDataValue w in PatientFusionItems)
                    PFItems.Add(new SyncWeight(w, MeasurementType.Kilograms));

                // Do Height now (in inches first)
                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 75, out FusionStatus);
                foreach (ClinicalDataValue h in PatientFusionItems)
                    PFItems.Add(new SyncHeight(h, MeasurementType.Inches));

                PatientFusionItems = API.GetClinicalDataValues(VendorID, PatientID, 76, out FusionStatus);
                foreach (ClinicalDataValue h in PatientFusionItems)
                    PFItems.Add(new SyncHeight(h, MeasurementType.Meters));

                // Compare each item in the two arrays and
                // see what they have in common
                foreach (SyncItem pfI in PFItems)
                {
                    // Loop through all of the values in HealthVault items
                    // and store any that match
                    foreach (SyncItem hvI in HVItems)
                    {
                        // Add the common weights from both to the list,
                        // which will make it easier to remove them
                        if (pfI == hvI)
                        {
                            CommonItems.Add(pfI);
                            CommonItems.Add(hvI);
                        }
                    }
                }

                // Remove all of the common items from each list
                foreach (SyncItem h in CommonItems)
                {
                    // Remove the value from each list (won't be removed if it doesn't exist)
                    HVItems.Remove(h);
                    PFItems.Remove(h);
                }

                // Add all of the Items in the HVHeights to Practice Fusion
                foreach (SyncItem i in HVItems)
                {
                    // Handle pressure syncs
                    if (i is SyncPressure)
                        SyncPressure((SyncPressure)i, API, VendorID, PatientID);
                    else
                        API.AddClinicalDataValue(VendorID, PatientID, i.PFItem, i.PFTypeId, out FusionStatus);
                }

                // Add all of the Items in the PFHeights to Health Vault
                foreach (SyncItem i in PFItems)
                    HRAccessor.NewItem(i.HVItem);

                Sync s = new Sync();
                s.SyncTime = DateTime.Now;
                s.IdUser = User.RestoreByPersonIdGuid(HVConnection.OfflinePersonId).IdUser;

                //System.Text.StringBuilder strB = new System.Text.StringBuilder();
                //foreach(SyncItem sI in HVItems)
                //{
                //    strB.Append(SyncManager.Serialize(sI));
                //    strB.Append("@@@---@@@");
                //}
                //s.SyncItemsHV = strB.ToString();
                //strB = new System.Text.StringBuilder();
                //foreach (SyncItem sI in PFItems)
                //{
                //    strB.Append(SyncManager.Serialize(sI));
                //    strB.Append("@@@---@@@");
                //}
                //s.SyncItemsPF = strB.ToString();
                s.SyncItemsHV = HVItems.Count.ToString();
                s.SyncItemsPF = PFItems.Count.ToString();
                s.save();

            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Generator a list of SyncPressure classes
        /// </summary>
        /// <param name="vendorID">The ID the Vendor sends</param>
        /// <param name="patientID">Patient ID</param>
        /// <returns>List of SyncPressure objects</returns>
        public static List<SyncPressure> GeneratorPressureList(CLinicalData API, string vendorID, string patientID)
        {
            OperationStatus status;
            List<SyncPressure> pressures = new List<SyncPressure>();

            ClinicalDataValue[] diastolics = API.GetClinicalDataValues(vendorID, patientID, 61, out status);
            ClinicalDataValue[] systolics = API.GetClinicalDataValues(vendorID, patientID, 62, out status);
            ClinicalDataValue[] bpms = API.GetClinicalDataValues(vendorID, patientID, 74, out status);

            // Loop through all of the items to see if we can't find
            // some that are part of a BloodPressure group
            foreach (ClinicalDataValue d in diastolics)
            {
                ClinicalDataValue systolic = null;
                ClinicalDataValue bpm = null;

                // Try to find the systolic with the same DateTime
                foreach (ClinicalDataValue s in systolics)
                {
                    if (d.CollectedDateTime == s.CollectedDateTime)
                        systolic = s;
                }

                // Try to find the BPM with the same DateTime
                foreach (ClinicalDataValue b in bpms)
                {
                    if (d.CollectedDateTime == b.CollectedDateTime)
                        bpm = b;
                }

                // See if we found both
                if (systolic != null && bpm != null)
                {
                    pressures.Add(
                        new SyncPressure(
                            d.CollectedDateTime.ToLocalTime(),
                            int.Parse(d.Value),
                            int.Parse(systolic.Value),
                            int.Parse(bpm.Value)));
                }
            }

            return pressures;
        }

        /// <summary>
        /// Sync a Pressure with Patient Fusion
        /// </summary>
        /// <param name="pressure">Pressure to Add</param>
        /// <param name="API">API</param>
        /// <param name="vendorID">VendorID</param>
        /// <param name="patientID">PatientID</param>
        public static void SyncPressure(SyncPressure pressure, CLinicalData API, string vendorID, string patientID)
        {
            ClinicalDataValue value;
            OperationStatus status;

            // Sync Diastolic first
            value = new ClinicalDataValue();
            value.CollectedDateTime = pressure.DateAdded.ToUniversalTime();
            value.Value = pressure.Diastolic.ToString();
            value.ID = 61;
            value.Source = "PatientFusion Sync";
            API.AddClinicalDataValue(vendorID, patientID, value, value.ID, out status);

            // Sync Systolic next
            value = new ClinicalDataValue();
            value.CollectedDateTime = pressure.DateAdded.ToUniversalTime();
            value.Value = pressure.Systolic.ToString();
            value.ID = 62;
            value.Source = "PatientFusion Sync";
            API.AddClinicalDataValue(vendorID, patientID, value, value.ID, out status);

            // Sync BPM last
            value = new ClinicalDataValue();
            value.CollectedDateTime = pressure.DateAdded.ToUniversalTime();
            value.Value = pressure.Pulse.ToString();
            value.ID = 74;
            value.Source = "PatientFusion Sync";
            API.AddClinicalDataValue(vendorID, patientID, value, value.ID, out status);
        }

        /// <summary>
        /// Serializes the SyncItem for personal usage or
        /// for storage in a database
        /// </summary>
        /// <returns>Serialized String</returns>
        public static string Serialize(object item)
        {
            // Serializing variables
            StringWriter stringWriter = new StringWriter();
            XmlSerializer serializer = new XmlSerializer(item.GetType());

            // Serialize to the stringWriter
            serializer.Serialize(stringWriter, item);

            // Write data to file
            string text = stringWriter.ToString();
            stringWriter.Close();

            return text;
        }

        /// <summary>
        /// Deserialize an object to a given type
        /// </summary>
        /// <param name="type">Object type</param>
        /// <param name="item">XML Item</param>
        /// <returns>Deserialized object</returns>
        public static object Deserialize(Type type, string item)
        {
            // Serializing variables
            StringReader stringReader = new StringReader(item);

            // Deserialize
            XmlSerializer serializer = new XmlSerializer(type);
            object o = serializer.Deserialize(stringReader);

            // Close stream
            stringReader.Close();

            return o;
        }
    }
}
